package com.ymwk.sm4;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.ymwk.base64.Base64Utils;


public class Sm4Utils  {
    public static final String ALGORITHM_NAME = "SM4";
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
    public static final String ALGORITHM_NAME_ECB_NOPADDING = "SM4/ECB/NoPadding";
    public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
    public static final String ALGORITHM_NAME_CBC_NOPADDING = "SM4/CBC/NoPadding";

    /**
     * SM4算法目前只支持128位（即密钥16字节）
     */
    public static final int DEFAULT_KEY_SIZE = 128;

    public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
        return generateKey(DEFAULT_KEY_SIZE);
    }

    public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(keySize, new SecureRandom());
        return kg.generateKey().getEncoded();
    }

    public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
        Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }
    
    public static boolean encrypt_ECB_Padding(byte[] key, String orgPath,String encryptPath)
    		throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
    		NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException {
    	Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
    	//定义输入流
    	InputStream in = new FileInputStream(orgPath);
    	//定义输出流
    	OutputStream out = new FileOutputStream(encryptPath);
    	CipherOutputStream cipherOut = new CipherOutputStream(out,cipher);
    	IOUtils.copy(in, cipherOut);
        cipherOut.close();
        in.close();
        out.close();
    	return true;
    }
    
    public static boolean encrypt_ECB_Padding(byte[] key, InputStream in,String encryptPath)
    		throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
    		NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException {
    	Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
    	//定义输出流
    	OutputStream out = new FileOutputStream(encryptPath);
    	CipherOutputStream cipherOut = new CipherOutputStream(out,cipher);
    	IOUtils.copy(in, cipherOut);
    	cipherOut.close();
    	in.close();
    	out.close();
    	return true;
    }
    
    public static boolean decrypt_ECB_Padding(byte[] key,String encryptPath,String DecryptPath)
    		throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
    		NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException , IOException{
    	Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
    	//定义输入流
    	InputStream in = new FileInputStream(encryptPath);
    	//定义输出流
    	OutputStream out = new FileOutputStream(DecryptPath);
    	CipherOutputStream cipherOut = new CipherOutputStream(out,cipher);
    	IOUtils.copy(in, cipherOut);
        cipherOut.close();
        in.close();
        out.close();
    	return  true;
    }
    public static boolean decrypt_ECB_Padding(byte[] key,InputStream in,String DecryptPath)
    		throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
    		NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException , IOException{
    	Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
    	//定义输出流
    	OutputStream out = new FileOutputStream(DecryptPath);
    	CipherOutputStream cipherOut = new CipherOutputStream(out,cipher);
    	IOUtils.copy(in, cipherOut);
    	cipherOut.close();
    	in.close();
    	out.close();
    	return  true;
    }

    
    public static byte[] encrypt_ECB_NoPadding(byte[] key, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt_ECB_NoPadding(byte[] key, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
        Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    public static byte[] encrypt_CBC_Padding(byte[] key, byte[] iv, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
            InvalidAlgorithmParameterException {
        Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt_CBC_Padding(byte[] key, byte[] iv, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            InvalidAlgorithmParameterException {
        Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
        return cipher.doFinal(cipherText);
    }

    public static byte[] encrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
            InvalidAlgorithmParameterException {
        Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.ENCRYPT_MODE, key, iv);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            InvalidAlgorithmParameterException {
        Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.DECRYPT_MODE, key, iv);
        return cipher.doFinal(cipherText);
    }

   

    private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
            throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            InvalidKeyException {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }

    private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(mode, sm4Key, ivParameterSpec);
        return cipher;
    }
    
    
    
    public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
    	String orgPath="C:\\Users\\Saxon\\Desktop\\test.txt";
    	String encryptPath="C:\\Users\\Saxon\\Desktop\\test-encryptAfter.txt";
    	String decryptPath="C:\\Users\\Saxon\\Desktop\\test-decryptAfter.txt";
    	Security.addProvider(new BouncyCastleProvider());
    	byte[] generateKey = generateKey();
    	System.out.println(Base64Utils.encode(generateKey));
    	boolean encrypt_ECB_Padding = encrypt_ECB_Padding(generateKey,orgPath,encryptPath);
    	System.out.println(encrypt_ECB_Padding);
    	FileInputStream fileInputStream = new FileInputStream(encryptPath);
    	ByteArrayOutputStream encryptOut = new ByteArrayOutputStream();
    	IOUtils.copy(fileInputStream, encryptOut);
    	byte[] decryptBytes = decrypt_ECB_Padding(generateKey,encryptOut.toByteArray());
    	FileOutputStream decryptOut = new FileOutputStream(decryptPath);
    	IOUtils.copy(new ByteArrayInputStream(decryptBytes), decryptOut);
    	encryptOut.close();
    	decryptOut.close();
    	System.out.println("完成");
    	
    	//大文件流解密处理
//    	boolean decrypt_ECB_Padding = decrypt_ECB_Padding(generateKey,encryptPath,decryptPath);
//    	System.out.println(decrypt_ECB_Padding);
    	
    	
//    	byte[] orgByte = FileUtils.readFileToByteArray(new File(orgPath));
//    	byte[] generateKey = generateKey();
//    	int length = orgByte.length;
//    	System.out.println(length);
//    	int max_encrypt_lenth_num = 61000000;
//    	int i =  length / max_encrypt_lenth_num;
//    	System.out.println("i="+i);
//    	int yushu = length % max_encrypt_lenth_num;
//    	FileOutputStream fileOutputStream = new FileOutputStream(encryptPath);
//    	for (int j = 0; j < i; j++) {
//    		byte[] tmpArray = new byte[max_encrypt_lenth_num];
//    		System.arraycopy(orgByte, j*max_encrypt_lenth_num, tmpArray, 0, max_encrypt_lenth_num);
//    		byte[] firstByte = encrypt_ECB_Padding(generateKey,tmpArray);
//    		fileOutputStream.write(firstByte);
//		}
//    	if(yushu != 0) {
//    		byte[] tmpArray = new byte[yushu];
//    		System.arraycopy(orgByte, length-yushu, tmpArray, 0, yushu);
//    		byte[] firstByte = encrypt_ECB_Padding(generateKey,tmpArray);
//    		fileOutputStream.write(firstByte);
//    	}
//    	fileOutputStream.close();
//		FileInputStream input = new FileInputStream(encryptPath);
//		int allEncryptLenth = input.available();
//		
		
		
		
    	
//		decrypt_ECB_Padding(generateKey, firstByte);
//		decrypt_ECB_Padding(generateKey, secondByte);
		
		//System.out.println(i);
	}
    
}
